package com.thghh.table;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.jidesoft.grid.ExpandableRow;
import org.jdesktop.swingx.tree.TreeModelSupport;
import org.jdesktop.swingx.treetable.TreeTableModel;

/**
 * @author Zhikang.Peng
 * @date 2021-01-12 15:31:42
 * @since 1.0
 */
public class TreeTableDataModel implements TreeTableModel {

    private Object root;
    private Class<?> entityClass;
    protected TreeModelSupport modelSupport;
    private BeanColumnAnnotationProcessor beanColumnProcessor;

    public TreeTableDataModel(Class<?> entityClass) {
        this(null, entityClass);
    }

    public TreeTableDataModel(DefaultMutableTreeNode root, Class<?> entityClass) {
        this.root = root;
        this.entityClass = entityClass;
        this.modelSupport = new TreeModelSupport(this);
        this.beanColumnProcessor = new BeanColumnAnnotationProcessor(entityClass);
    }

    @Override
    public Object getRoot() {
        return root;
    }

    public DefaultMutableTreeNode getDefuleRoot() {
        return (DefaultMutableTreeNode) root;
    }

    /**
     * Sets the root to <code>root</code>. A null <code>root</code> implies the
     * tree is to display nothing, and is legal.
     */
    public void setRoot(DefaultMutableTreeNode root) {
        this.root = root;
        modelSupport.fireNewRoot();
    }

    @Override
    public boolean isLeaf(Object node) {
        return getChildCount(node) == 0;
    }

    @Override
    public void valueForPathChanged(TreePath path, Object newValue) {
        if (path.getPathComponent(0) != root) {
            throw new IllegalArgumentException("invalid path");
        }

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
        node.setUserObject(newValue);

        modelSupport.firePathChanged(path);
    }

    @Override
    public int getHierarchicalColumn() {
        if (getColumnCount() == 0) {
            return -1;
        }

        return 0;
    }

    @Override
    public boolean isCellEditable(Object node, int columnIndex) {
        return beanColumnProcessor.getBeanColumn(columnIndex).editable();
    }

    @Override
    public int getColumnCount() {
        return beanColumnProcessor.getColumnCount();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return beanColumnProcessor.getPropertyDescriptor(columnIndex).getPropertyType();
    }

    @Override
    public String getColumnName(int column) {
        return beanColumnProcessor.getBeanColumn(column).name();
    }

    @Override
    public Object getValueAt(Object node, int columnIndex) {
        Object obj = null;
        try {
            DefaultMutableTreeNode treenode = (DefaultMutableTreeNode) node;
            obj = beanColumnProcessor.getPropertyValue(treenode.getUserObject(), columnIndex);
        } catch (Exception e) {
            throw new CheckException(e);
        }
        return obj;
    }

    @Override
    public void setValueAt(Object aValue, Object node, int columnIndex) {
        try {
            DefaultMutableTreeNode treenode = (DefaultMutableTreeNode) node;
            beanColumnProcessor.setPropertyValue(treenode.getUserObject(), columnIndex, aValue);
            valueForPathChanged(new TreePath(treenode.getPath()), treenode.getUserObject());
        } catch (Exception e) {
            throw new CheckException(e);
        }
    }

    @Override
    public Object getChild(Object parent, int index) {
        return ((TreeNode) parent).getChildAt(index);
    }

    @Override
    public int getChildCount(Object parent) {
        return ((TreeNode) parent).getChildCount();
    }

    @Override
    public int getIndexOfChild(Object parent, Object child) {
        for (int i = 0; i < getChildCount(parent); i++) {
            if (getChild(parent, i).equals(child)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Builds the parents of node up to and including the root node, where the
     * original node is the last element in the returned array. The length of
     * the returned array gives the node's depth in the tree.
     *
     * @param aNode
     *            the TreeNode to get the path for
     */
    public TreeNode[] getPathToRoot(TreeNode aNode) {
        return getPathToRoot(aNode, 0);
    }

    /**
     * Builds the parents of node up to and including the root node, where the
     * original node is the last element in the returned array. The length of
     * the returned array gives the node's depth in the tree.
     *
     * @param aNode
     *            the TreeNode to get the path for
     * @param depth
     *            an int giving the number of steps already taken towards the
     *            root (on recursive calls), used to size the returned array
     * @return an array of TreeNodes giving the path from the root to the
     *         specified node
     */
    protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
        TreeNode[] retNodes;
        // This method recurses, traversing towards the root in order
        // size the array. On the way back, it fills in the nodes,
        // starting from the root and working back to the original node.

        /*
         * Check for null, in case someone passed in a null node, or they passed
         * in an element that isn't rooted at root.
         */
        if (aNode == null) {
            if (depth == 0)
                return null;
            else
                retNodes = new TreeNode[depth];
        } else {
            depth++;
            if (aNode == root)
                retNodes = new TreeNode[depth];
            else
                retNodes = getPathToRoot(aNode.getParent(), depth);
            retNodes[retNodes.length - depth] = aNode;
        }
        return retNodes;
    }

    /**
     * {@inheritDoc}
     */
    public void addTreeModelListener(TreeModelListener l) {
        modelSupport.addTreeModelListener(l);
    }

    /**
     * {@inheritDoc}
     */
    public void removeTreeModelListener(TreeModelListener l) {
        modelSupport.removeTreeModelListener(l);
    }

    /**
     * Returns an array of all the <code>TreeModelListener</code>s added to this
     * JXTreeTable with addTreeModelListener().
     *
     * @return all of the <code>TreeModelListener</code>s added or an empty
     *         array if no listeners have been added
     */
    public TreeModelListener[] getTreeModelListeners() {
        return modelSupport.getTreeModelListeners();
    }

    /**
     * Invoke this method if you've modified the {@code TreeNode}s upon which
     * this model depends. The model will notify all of its listeners that the
     * model has changed below the given node.
     *
     * @param node
     *            the node below which the model has changed
     */
    public void reload(DefaultMutableTreeNode node) {
        if (node != null) {
            valueForPathChanged(toTreePath(node), node.getUserObject());
        }
    }

    /**
     * Invoked this to insert newChild at location index in parents children.
     * This will then message nodesWereInserted to create the appropriate event.
     * This is the preferred way to add children as it will create the
     * appropriate event.
     */
    public void insertNodeInto(DefaultMutableTreeNode newChild, DefaultMutableTreeNode parent, int index) {
        parent.insert(newChild, index);
        modelSupport.fireChildAdded(toTreePath(parent), index, newChild);
    }

    /**
     * Invoked this to insert newChild at location index in parents children.
     * This will then message nodesWereInserted to create the appropriate event.
     * This is the preferred way to add children as it will create the
     * appropriate event.
     */
    public void insertNodeInto(DefaultMutableTreeNode newChild, DefaultMutableTreeNode parent) {
        insertNodeInto(newChild, parent, parent.getChildCount());
    }

    /**
     * remove child from parent
     * 
     * @param child
     * @param parent
     * @author Zhikang.Peng
     * @date 2021-01-21 10:38:03
     * @since 1.0
     */
    public void removeNodeInto(DefaultMutableTreeNode child, DefaultMutableTreeNode parent) {
        int index = parent.getIndex(child);
        if (index != -1) {
            parent.remove(index);
            modelSupport.fireChildRemoved(toTreePath(parent), index, child);
        }
    }

    public TreePath toTreePath(DefaultMutableTreeNode node) {
        return new TreePath(node.getPath());
    }

}
